﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using memcached;

namespace CacheExamples
{
    public static class SyncTest
    {
        private static byte[] data = new byte[4096];
        private static volatile bool running;

        public static void RunTest()
        {
            #region Prepare activities
            Action<ICache> t1 = (mc) =>
            {
                bool success;
                using (var c = new SyncMemCache(mc))
                {
                    byte[] d = c.Get<byte[]>("value1", out success);// sync on value1 key
                    // simulate operation
                    if (d == null)
                        d = data;
                    if (d[0] == 255)
                        d[0] = 0;
                    else
                        d[0] = (byte)(d[0] + 1);
                    System.Threading.Thread.Sleep(5);
                    bool res = c.Set<byte[]>("value1",
                        (cl, key) => cl.Set(key, d));// sync on value1 key
                    if (!res)
                        System.Diagnostics.Trace.TraceError("T1 Failed");
                }

                int v = DistMemCache.PerformOnce<int>(mc, 5, "test", "DistVal1", (_c) =>
                {
                    // simulate lengthy operation that has to be performed once per whole system
                    // i.e. we don't want it to perform twice in parallel
                    // note: the max timeout has to be larger than any expected length of the operation because of thread starving.
                    long uniq = ((ICacheAdvanced)_c).Increment("once", 10);// we initialize below..
                    if (uniq != 1)// ..so this is guaranteed
                        throw new Exception();
                    System.Threading.Thread.Sleep(1000);
                    ((ICacheAdvanced)_c).Decrement("once");
                    return System.Threading.Thread.CurrentThread.ManagedThreadId;
                });
            };

            Action<ICacheAdvanced> t2 = (mc) =>
            {
                bool success;
                using (var c = new SyncMemCache(mc))
                {
                    string s = c.Get<string>("value2", out success);
                    // simulate operation
                    if (s == null || s.Length >= 1024)
                        s = string.Empty;
                    s += "0";
                    System.Threading.Thread.Sleep(6);
                    bool res = c.Set<string>("value2",
                        (cl, key) => cl.Set(key, s));
                    if (!res)
                        System.Diagnostics.Trace.TraceError("T2 Failed");
                }

                using (var tc = new TransMemCache(mc))
                {
                    // only this thread is performing that
                    bool res = tc.PerformOnExisting<string>("transVal", (s) =>
                    {
                        if (s.Length >= 1024)
                            s = string.Empty;
                        s += "0";
                        return s;
                    });
                    if (!res)
                        System.Diagnostics.Trace.TraceWarning("PerformOnExisting Failed");
                }

                bool mr = DistMemCache.PerformCrossProcess(1000, "myTest", () =>
                {
                    bool x;
                    int v = mc.Get<int>("global1", out x);
                    if (v == 1024)
                        v = 0;
                    else
                        v++;
                    System.Threading.Thread.Sleep(7);
                    bool r = mc.Set("global1", v);
                    if (!r)
                        System.Diagnostics.Trace.TraceError("T2 Failed");
                });
                if (!mr)
                    System.Diagnostics.Trace.TraceWarning("PerformCrossProcess Failed");
                System.Threading.Thread.Sleep(5);
            };

            Action<ICacheAdvanced> t3 = (mc) =>
            {
                using (var c = new SyncMemCache(mc))
                {
                    long v = mc.Increment("value3", 1);
                    if (v < 1)
                        System.Diagnostics.Trace.TraceError("T3 Failed");
                    else
                    {
                        System.Threading.Thread.Sleep(7);
                        if (v == 1024)
                        {
                            bool res = c.Set<string>("value3",
                                (cl, key) => cl.Set(key, "0"));// sync on value3 key
                            if (!res)
                                System.Diagnostics.Trace.TraceError("T4 Failed");
                        }
                    }
                }

                // perform in write-lock
                TransMemCache.Perform<string>(mc, "var1", (s) =>
                {
                    if (s == null || s.Length == 1024)
                    {
                        System.Diagnostics.Trace.TraceInformation("Transact loop");
                        s = string.Empty;
                    }
                    else
                        s += "0";
                    return s;
                });
            };
            #endregion

            #region Prepare threads
            System.Threading.WaitCallback w1 = (o) =>
            {
                CachePool pool = (CachePool)o;
                int i = 0;
                System.Diagnostics.Trace.TraceInformation("Starting W1");
                while (running)
                {
                    pool.Perform(100, (c) => t1(c));
                    pool.Perform(100, (c) => t2((ICacheAdvanced)c));
                    pool.Perform(100, (c) => t3((ICacheAdvanced)c));
                    System.Threading.Thread.Sleep(11);
                    i++;
                };
                System.Diagnostics.Trace.TraceInformation("Stopped W1 at " + i);
            };

            System.Threading.WaitCallback w2 = (o) =>
            {
                CachePool pool = (CachePool)o;
                int i = 0;
                System.Diagnostics.Trace.TraceInformation("Starting W2");
                while (running)
                {
                    pool.Perform(100, (c) => t2((ICacheAdvanced)c));
                    pool.Perform(100, (c) => t1(c));
                    pool.Perform(100, (c) => t3((ICacheAdvanced)c));
                    System.Threading.Thread.Sleep(10);
                    i++;
                };
                System.Diagnostics.Trace.TraceInformation("Stopped W2 at " + i);
            };

            System.Threading.WaitCallback w3 = (o) =>
            {
                CachePool pool = (CachePool)o;
                int i = 0;
                System.Diagnostics.Trace.TraceInformation("Starting W3");
                while (running)
                {
                    pool.Perform(100,
                        (c) => t3((ICacheAdvanced)c),
                        (c) => t1(c),
                        (c) => t2((ICacheAdvanced)c));
                    System.Threading.Thread.Sleep(9);
                    i++;
                };
                System.Diagnostics.Trace.TraceInformation("Stopped W3 at " + i);
            };
            #endregion

            running = true;
            CachePool mpool = new CachePool(2);
            try
            {
                // initialize
                mpool.Perform(100, (m) => m.Set("once", "0"));
                mpool.Perform(100, (m) => m.Set("transVal", ""));
                // run
                Console.WriteLine("Starting sychronization test.. press Enter to stop");
                System.Threading.ThreadPool.QueueUserWorkItem(w1, mpool);
                System.Threading.ThreadPool.QueueUserWorkItem(w2, mpool);
                System.Threading.ThreadPool.QueueUserWorkItem(w3, mpool);
                Console.ReadLine();
                running = false;
            }
            finally
            {
                mpool.Dispose();
            }
        }
    }
}